#include "znx_resolver.h"
#include "comm/znx_file.h"
#include "comm/znx_handles.h"
#include "comm/znx_random.h"

#define ZNX_DEF_RESOLVER_FILE_NAME      "/etc/resolv.conf"
#define ZNX_DEF_RESOLVER_FILE_MAX_SIZE  8192
#define ZNX_RESOLVER_UDP_SIZE           4096

#define ZNX_RESOLVE_SECTION_AN          1
#define ZNX_RESOLVE_SECTION_NS          2
#define ZNX_RESOLVE_SECTION_AR          3

typedef struct {
    u_char          ident_hi;
    u_char          ident_lo;
    u_char          flags_hi;
    u_char          flags_lo;
    u_char          nqs_hi;
    u_char          nqs_lo;
    u_char          nan_hi;
    u_char          nan_lo;
    u_char          nns_hi;
    u_char          nns_lo;
    u_char          nar_hi;
    u_char          nar_lo;
} znx_resolver_hdr_t;

typedef struct {
    u_char          type_hi;
    u_char          type_lo;
    u_char          class_hi;
    u_char          class_lo;
} znx_resolver_qs_t;

typedef struct {
    u_char  type_hi;
    u_char  type_lo;
    u_char  class_hi;
    u_char  class_lo;
    u_char  ttl[4];
    u_char  len_hi;
    u_char  len_lo;
} znx_resolver_an_t;


static void znx_resolver_resp_invalid_result(znx_resolver_task_t *task,
    int eno);
static void znx_resolver_do_task(znx_dispatch_event_t *dispatch_event);
static void znx_resolver_task_connect(znx_resolver_t *resolver,
    znx_resolver_task_t *task, znx_bool_t tcp_query);
static int znx_resolver_parse_response(znx_resolver_task_t *task,
    u_char *buf, size_t n, znx_resolver_result_t **result);

static void
znx_resolver_add_nameserver(znx_resolver_t *resolver, znx_str_t *ns)
{
    int                         port = 0;
    znx_address_t               address;
    znx_resolver_nameserver_t   *nameserver;
    znx_bool_t                  res;

    if (ns->len > ZNX_SOCKADDR_SIZE) {
        return;
    }

    if (znx_str_maybe_is_ipv4(ns) || ns->data[0] == '[') {
        res = znx_address_parser(ns->data,
            ns->len, &address, &port);
        if (!res) {
            return;
        }
        if (port == 0) {
            znx_address_set_port(&address, 53);
        }
        nameserver = znx_array_push(resolver->names);
        nameserver->address = address;
        bzero(&nameserver->bind_address, sizeof(znx_address_t));
        znx_memcpy(nameserver->address_text, ns->data, ns->len);
        nameserver->address_text[ns->len] = '\0';
        bzero(nameserver->bind_address_text, ZNX_SOCKADDR_SIZE);
        return;
    }

    // '[' + ']' + '\0'
    if (ns->len > ZNX_SOCKADDR_SIZE - 3) {
        return;
    }

    u_char                      buf[ZNX_SOCKADDR_SIZE];
    size_t                      len;

    buf[0] = '[';
    znx_memcpy(buf + 1, ns->data, ns->len);
    buf[1 + ns->len] = ']';
    buf[2 + ns->len] = '\0';
    len = 2 + ns->len;

    res = znx_address_parser(buf, len, &address, &port);
    if (!res) {
        return;
    }
    if (port == 0) {
        znx_address_set_port(&address, 53);
    }
    nameserver = znx_array_push(resolver->names);
    nameserver->address = address;
    bzero(&nameserver->bind_address, sizeof(znx_address_t));
    znx_memcpy(nameserver->address_text, ns->data, ns->len);
    nameserver->address_text[ns->len] = '\0';
    bzero(nameserver->bind_address_text, ZNX_SOCKADDR_SIZE);
    return;
}


static znx_bool_t
znx_resolver_load_def_names(znx_resolver_t *resolver)
{
    char            *err;
    int64_t         len;

    znx_t_buf_grow(ZNX_DEF_RESOLVER_FILE_MAX_SIZE);
    len = znx_read_file(ZNX_DEF_RESOLVER_FILE_NAME,
        t_buf, t_buf_size, &err);

    if (len < 0) {
        ZNX_DEF_LOG_ERROR(NULL, "read %s failed, err: %s",
            ZNX_DEF_RESOLVER_FILE_NAME, *err);
        return ZNX_FALSE;
    }

    size_t          i = 0;
    size_t          j = 0;
    size_t          data_len = (size_t)len;
    znx_str_t       line;
    while (1) {
        ZNX_STR_SET_NULL(&line);

        if (i >= data_len) {
            break;
        }

        line.data = t_buf + i;
        while (1) {
            if (i >= data_len || t_buf[i] == '\n') {
                line.len = (size_t)(t_buf + i - line.data);
                // skip char '\n'
                if (t_buf[i] == '\n') {
                    i++;
                }

                break;
            }

            i++;
        }

        // skip space char ' ', '\t'
        for (j = 0; j < line.len; j++) {
            if (line.data[j] == ' ' || line.data[j] == '\t') {
                continue;
            }

            break;
        }

        line.data += j;
        line.len -= j;

        if (line.len < sizeof("nameserver") - 1 ||
            znx_strncmp(line.data, "nameserver", sizeof("nameserver") - 1) != 0) {
            continue;
        }

        line.data += sizeof("nameserver") - 1;
        line.len -= sizeof("nameserver") - 1;

        // skip space char ' ', '\t'
        for (j = 0; j < line.len; j++) {
            if (line.data[j] == ' ' || line.data[j] == '\t') {
                continue;
            }

            break;
        }

        line.data += j;
        line.len -= j;

        znx_resolver_add_nameserver(resolver, &line);
    }

    return ZNX_TRUE;
}


static inline void
znx_resolver_add_names(znx_resolver_t *resolver, znx_array_t *nameservers)
{
    znx_resolver_nameserver_t *ns;
    znx_resolver_nameserver_t *nameserver;
    znx_resolver_nameserver_t *nameserverp = nameservers->elts;
    for (size_t i = 0; i < nameservers->nelts; i++) {
        nameserver = nameserverp + i;

        ns = znx_array_push(resolver->names);
        ns->address = nameserver->address;
        ns->bind_address = nameserver->bind_address;
        znx_memcpy(ns->address_text, nameserver->address_text, ZNX_SOCKADDR_SIZE);
        znx_memcpy(ns->bind_address_text, nameserver->bind_address_text, ZNX_SOCKADDR_SIZE);
    }
}


znx_resolver_t *
znx_resolver_create(znx_array_t *nameservers, int64_t max_cache_item)
{
    znx_resolver_t *resolver =
        znx_thread_calloc(sizeof(znx_resolver_t));
    znx_cleanup_list_init(&resolver->cleanup);

    resolver->names =
        znx_array_create(8, sizeof(znx_resolver_nameserver_t));
    znx_cleanup_list_add(&resolver->cleanup,
        znx_array_destroy_handle, resolver->names);

    if (nameservers != NULL) {
        znx_resolver_add_names(resolver, nameservers);
    }

    if (resolver->names->nelts == 0) {
        if (!znx_resolver_load_def_names(resolver)) {
            znx_cleanup_list_exec(&resolver->cleanup);
            return NULL;
        }
    }

    if (max_cache_item > 0) {
        if (max_cache_item < 128) {
            max_cache_item = 128;
        }

        resolver->has_cache = ZNX_TRUE;

        for (int i = 0; i < ZNX_RESOLVER_CACHE_SLOT; i++) {
            znx_spinlock_init(&resolver->locks[i]);
            resolver->slrus[i] = znx_slru_create(2, max_cache_item, 0, NULL,
                znx_resolver_result_free,
                znx_resolver_result_cmp);
        }
    }

    znx_single_flight_init(&resolver->sf, znx_resolver_task_cmp, NULL,
        znx_resolver_result_hold_handler);

    return resolver;
}


void
znx_resolver_destroy(znx_resolver_t *resolver)
{
    if (resolver->has_cache) {
        for (int i = 0; i < ZNX_RESOLVER_CACHE_SLOT; i++) {
            znx_slru_destroy(resolver->slrus[i]);
        }
    }
    znx_cleanup_list_exec(&resolver->cleanup);
    znx_thread_free(resolver);
}


static void
znx_resolver_resp_invalid_result(znx_resolver_task_t *task,
    int eno)
{
    znx_resolver_result_t *result =
        znx_resolver_result_create();
    result->eno = eno;
    task->last_eno = eno;
    task->result = result;

    if (task->ctx != NULL) {
        znx_resolver_task_ctx_destroy(task->ctx);
        task->ctx = NULL;
    }

    task->handler(task);
}


void
znx_resolver_task_done(znx_dispatch_event_t *dispatch_event)
{
    znx_resolver_task_t *task = dispatch_event->data;
    znx_resolver_result_t *result = dispatch_event->save_data_2;

    if (result == NULL) {
        result = znx_resolver_result_create();
        if (task->last_eno == 0) {
            task->last_eno = ZNX_RESOLVE_UNKNOW_ERR;
        }
        result->eno = task->last_eno;
    }

    task->result = result;

    if (task->ctx != NULL) {
        znx_resolver_task_ctx_destroy(task->ctx);
        task->ctx = NULL;
    }

    task->handler(task);
}


static znx_bool_t
znx_resolver_build_name_query(znx_resolver_task_t *task)
{
    u_char                  *p, *s, *q;
    size_t                  len, nlen;
    uint64_t                ident;
    znx_resolver_qs_t       *qs;
    znx_resolver_hdr_t      *query;

    if (task->query.len == 0 || task->query.data[0] == '.') {
        return ZNX_FALSE;
    }

    /* "www.example.com" convert to "\3www\7example\3com\0" */
    nlen = 1 + task->query.len + 1;
    len = sizeof(znx_resolver_hdr_t) + nlen + sizeof(znx_resolver_qs_t);

    // for tcp query. reserve the first two bytes.
    q = znx_thread_malloc(len + 2);
    p = q + 2;
    task->ctx->query = p;
    task->ctx->qlen = len;

    // for tcp query.
    // the first two bytes are used to store the qlen.
    q[0] = (u_char)(len >> 8);
    q[1] = (u_char)(len & 0xff);

    query = (znx_resolver_hdr_t *)p;
    ident = (uint64_t)znx_random_n(65535);
    query->ident_hi = (u_char) ((ident >> 8) & 0xff);
    query->ident_lo = (u_char) (ident & 0xff);
    /* recursion query */
    query->flags_hi = 1; query->flags_lo = 0;
    /* one question */
    query->nqs_hi = 0; query->nqs_lo = 1;
    query->nan_hi = 0; query->nan_lo = 0;
    query->nns_hi = 0; query->nns_lo = 0;
    query->nar_hi = 0; query->nar_lo = 0;

    p += sizeof(znx_resolver_hdr_t) + nlen;

    qs = (znx_resolver_qs_t *)p;

    /* query type */
    qs->type_hi = 0; qs->type_lo = (u_char)task->qtype;

    /* IN query class */
    qs->class_hi = 0; qs->class_lo = 1;

    /* convert "www.example.com" to "\3www\7example\3com\0" */
    len = 0;
    p--;
    *p-- = '\0';

    for (s = task->query.data + task->query.len - 1;
        s >= task->query.data; s--) {
        if (*s != '.') {
            *p = *s;
            len++;

        } else {
            if (len == 0 || len > 255) {
                return ZNX_FALSE;
            }

            *p = (u_char)len;
            len = 0;
        }

        p--;
    }

    if (len == 0 || len > 255) {
        return ZNX_FALSE;
    }

    *p = (u_char)len;


    return ZNX_TRUE;
}


static void
znx_resolver_do_tcp_query(znx_dispatch_event_t *dispatch_event)
{
    znx_resolver_task_t *task = dispatch_event->data;
    znx_resolver_t *resolver = task->owner_resolver;

    if (task->ctx == NULL) {
        task->ctx = znx_resolver_task_ctx_create();
    } else {
        znx_resolver_task_ctx_reset(task->ctx);
    }

    znx_resolver_task_connect(resolver, task, 1);
}


static void
znx_resolver_do_query_done(znx_resolver_task_t *task, int eno,
    znx_resolver_result_t *result)
{
    znx_resolver_t          *resolver = task->owner_resolver;
    znx_resolver_task_ctx_t *ctx = task->ctx;
    znx_conn_t              *conn = ctx->conn;
    int64_t                 ttl = 0;

    znx_event_loop_remove_conn(conn);

    // FAILED
    if (eno != 0) {
        znx_conn_free(conn);
        ctx->conn = NULL;

        task->last_eno = eno;

        // close conn.
        ctx->err = 1;
        
        if (!ctx->tcp_query && eno == ZNX_RESOLVE_TRUNC_ERR) {
            znx_resolver_do_tcp_query(&task->dispatch_event);
            return;
        }

        znx_resolver_do_task(&task->dispatch_event);
        return;
    }

    ttl = task->cache_time_ms > 0 ? task->cache_time_ms : result->ttl * 1000;

    if (ttl > 3000) {
        result->crc = task->crc;
        result->key.data = znx_thread_malloc(task->key.len);
        znx_cleanup_list_add(&result->cleanup_list,
            znx_thread_free_handle, result->key.data);
        znx_memcpy(result->key.data, task->key.data, task->key.len);
        result->key.len = task->key.len;
        result->expire_time =
            znx_usec_2_msec(get_cache_time()) + ttl;

        znx_resolver_result_hold(result);

        int slot = task->crc % ZNX_RESOLVER_CACHE_SLOT;
        znx_spinlock_lock(&resolver->locks[slot]);
        znx_slru_put(resolver->slrus[slot], result);
        znx_spinlock_unlock(&resolver->locks[slot]);
    }

    znx_single_flight_notify(&resolver->sf, &task->dispatch_event, result);
}


static void
znx_resolver_task_timeout(znx_event_t *event)
{
    znx_conn_t *conn = event->data;
    znx_resolver_task_t *task = conn->data;
    znx_resolver_do_query_done(task, ZNX_RESOLVE_TIMEDOUT, NULL);
}


static void
znx_resolver_tcp_recv(znx_event_t *event)
{
    if (event->timedout) {
        znx_resolver_task_timeout(event);
        return;
    }

    znx_conn_t              *conn = event->data;
    znx_resolver_task_t     *task = conn->data;
    znx_resolver_task_ctx_t *ctx = task->ctx;
    znx_result_t            ret;

    while (ctx->recv_len < 2) {
        ret = znx_conn_read(conn,
            ctx->recv_len_buf,
            2 - ctx->recv_len);
        if (ret == ZNX_AGAIN) {
            znx_event_loop_enable_read(conn);
            return;
        }

        // ERROR or EOF.
        if (ret < 0 || ret == 0) {
            task->last_eno = ZNX_RESOLVE_SERVFAIL;
            // remove_conn in query_done function.
            // so we don't need to disable read event.
            znx_resolver_do_query_done(task, ZNX_RESOLVE_SERVFAIL, NULL);
            return;
        }

        ctx->recv_len += (size_t)ret;
    }

    if (!ctx->recv_data_buf) {
        int len_hi = ctx->recv_len_buf[0];
        int len_lo = ctx->recv_len_buf[1];
        int len = (len_hi << 8) + (len_lo & 0xff);
        ctx->expect_data_len = (size_t)len;
        ctx->recv_data_buf = znx_thread_malloc((size_t)len);
    }

    while (ctx->recv_len < 2 + ctx->expect_data_len) {
        ret = znx_conn_read(conn,
            ctx->recv_data_buf + ctx->recv_len - 2,
            ctx->expect_data_len - ctx->recv_len + 2);
        if (ret == ZNX_AGAIN) {
            znx_event_loop_enable_read(conn);
            return;
        }

        if (ret < 0 || ret == 0) {
            task->last_eno = ZNX_RESOLVE_SERVFAIL;
            // remove_conn in query_done function.
            // so we don't need to disable read event.
            znx_resolver_do_query_done(task, ZNX_RESOLVE_SERVFAIL, NULL);
            return;
        }

        ctx->recv_len += (size_t)ret;
    }

    znx_resolver_result_t   *result;
    int                     eno;
    eno = znx_resolver_parse_response(task,
        ctx->recv_data_buf,
        ctx->expect_data_len,
        &result);
    if (eno == 0) {
        znx_resolver_do_query_done(task, 0, result);
        return;
    }

    task->last_eno = eno;
    znx_resolver_do_query_done(task, eno, NULL);
}


static void
znx_resolver_tcp_send(znx_event_t *event)
{
    znx_conn_t                  *conn;
    znx_resolver_task_t         *task;
    znx_resolver_task_ctx_t     *ctx;
    znx_result_t                ret;

    conn = event->data;
    task = conn->data;
    ctx = task->ctx;

    while (ctx->send_len < ctx->qlen + 2) {
        ret = znx_conn_write(conn,
            ctx->query - 2 + ctx->send_len,
            ctx->qlen + 2 - ctx->send_len);
        if (ret == ZNX_AGAIN) {
            znx_event_loop_enable_write(conn);
            return;
        }

        // ret < 0 mean error, ret == 0 mean eof in read/rewrite function.
        if (ret < 0 || ret == 0) {
            task->last_eno = ZNX_RESOLVE_SERVFAIL;
            // remove_conn in query_done function.
            // so we don't need to disable write event.
            znx_resolver_do_query_done(task, ZNX_RESOLVE_SERVFAIL, NULL);
            return;
        }

        ctx->send_len += (size_t)ret;
    }

    znx_event_loop_disable_write(conn);
    // enable read.
    znx_event_loop_enable_read(conn);
}


// return error code
// save decode name to t_buf.
static int
znx_resolver_decode_name(u_char *buf, size_t n, size_t *posp)
{
    size_t  p = *posp;
    size_t  pos = *posp;
    size_t  len = 0;
    int     nptrs = 0;

    while (nptrs < 128) {
        if (p >= n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        u_char fst = buf[p];
        if (fst == 0) {
            if (nptrs == 0) {
                pos = pos + 1;
            }

            break;
        }

        if ((fst & 0xc0) != 0) {
            // being a point
            if (nptrs == 0) {
                pos = pos + 2;
            }

            nptrs = nptrs + 1;

            if (p+1 >= n) {
                return ZNX_RESOLVE_TRUNC_ERR;
            }

            u_char snd = buf[p+1];

            p = ((size_t)(fst & 0x3f) << 8) + (size_t)snd;
            continue;
        }

        // copy memcpy(t_buf, buf + p + 1, fst)
        if (p + 1 + (size_t)fst >= n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        len += (size_t)fst + 1; // \3www. = 3 + '.'

        p = p + (size_t)fst + 1;

        if (nptrs == 0) {
            pos = p;
        }
    }

    if (nptrs >= 128) {
        return ZNX_RESOLVE_SERVFAIL;
    }

    znx_t_buf_grow(len);

    p = *posp;
    pos = *posp;
    nptrs = 0;
    len = 0;

    while (nptrs < 128) {
        if (p >= n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        u_char fst = buf[p];
        if (fst == 0) {
            if (nptrs == 0) {
                pos = pos + 1;
            }

            break;
        }

        if (fst & 0xc0) {
            // being a point
            if (nptrs == 0) {
                pos = pos + 2;
            }

            nptrs = nptrs + 1;

            if (p+1 >= n) {
                return ZNX_RESOLVE_TRUNC_ERR;
            }

            u_char snd = buf[p+1];

            p = ((size_t)(fst & 0x3f) << 8) + (size_t)snd;
            continue;
        }

        if (p + 1 + (size_t)fst >= n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        znx_memcpy(t_buf+len, buf+p+1, (size_t)fst);
        len += (size_t)fst;
        t_buf[len] = '.';
        len += 1;

        p = p + (size_t)fst + 1;

        if (nptrs == 0) {
            pos = p;
        }
    }

    if (len > 0) {
        len -= 1;
    }
    t_buf[len] = '\0';

    *posp = pos;

    return 0;
}


static int
znx_resolver_parse_section(int section, u_char *buf, size_t n, size_t *posp,
    int size, znx_resolver_result_t *res, znx_bool_t should_skip)
{
    int                         code = 0;
    size_t                      pos = *posp;
    znx_str_t                   name;
    znx_resolver_result_item_t  *item;
    size_t                      str_len;


    for (int i = 0; i < size; i++) {
        code = znx_resolver_decode_name(buf, n, &pos);
        if (code != 0) {
            return code;
        }

        str_len = znx_strlen(t_buf);
        name.data = znx_thread_malloc(str_len + 1);
        znx_memcpy(name.data, t_buf, str_len);
        name.data[str_len] = '\0';
        name.len = (size_t)str_len;
        znx_cleanup_list_add(&res->cleanup_list, znx_thread_free_handle, name.data);

        if (pos + 10 > n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        int type_hi = buf[pos];
        int type_lo = buf[pos + 1];
        int type = (type_hi << 8) + type_lo;

#if 0
        item->section = section;
        item->type = type;

        int class_hi = buf[pos + 2];
        int class_lo = buf[pos + 3];
        int class = class_hi << 8 + class_lo;
        item->class = class;
#endif

        int byte1 = buf[pos + 4];
        int byte2 = buf[pos + 5];
        int byte3 = buf[pos + 6];
        int byte4 = buf[pos + 7];

        res->ttl = (byte1 << 24) + (byte2 << 16)
            + (byte3 << 8) + byte4;

        int len_hi = buf[pos + 8];
        int len_lo = buf[pos + 9];
        int len = (len_hi << 8) + len_lo;

        // type, class, ttl, len
        pos = pos + 10;

        if (pos + (size_t)len > n) {
            return ZNX_RESOLVE_TRUNC_ERR;
        }

        if (type == ZNX_RESOLVE_A) {
            if (len != 4) {
                return ZNX_RESOLVE_FORMERR;
            }

            int addr_bytes_1 = buf[pos];
            int addr_bytes_2 = buf[pos+1];
            int addr_bytes_3 = buf[pos+2];
            int addr_bytes_4 = buf[pos+3];

            u_char *tmp = znx_thread_malloc(ZNX_SOCKADDR_SIZE);
            znx_cleanup_list_add(&res->cleanup_list, znx_thread_free_handle, tmp);
            u_char *end = znx_snprintf(tmp, ZNX_SOCKADDR_SIZE,
                "%d.%d.%d.%d", addr_bytes_1, addr_bytes_2,
                addr_bytes_3, addr_bytes_4);

            item = znx_array_push(res->arr);
            item->type = type;
            item->name = name;
            item->data.data = tmp;
            item->data.len = (size_t)(end - tmp);

            pos += 4;

        } else if (type == ZNX_RESOLVE_AAAA) {
            if (len != 16) {
                return ZNX_RESOLVE_FORMERR;
            }

            int     addr_types[16];
            char    tmp_buf[8][8];
            u_char  *tmp = znx_thread_malloc(ZNX_SOCKADDR_SIZE);
            znx_cleanup_list_add(&res->cleanup_list, znx_thread_free_handle, tmp);

            for (size_t j = 0; j < 16; j++) {
                addr_types[j] = buf[pos+j];
            }

            for (size_t j = 0; j < 16; j += 2) {
                int a = addr_types[j];
                int b = addr_types[j + 1];
                if (a == 0) {
                    snprintf(&tmp_buf[j/2][0], 8, "%x", b);
                } else {
                    snprintf(&tmp_buf[j/2][0], 8, "%x%02x", a, b);
                }
            }

            u_char *end = znx_snprintf(tmp, ZNX_SOCKADDR_SIZE,
                "%s:%s:%s:%s:%s:%s:%s:%s",
                tmp_buf[0], tmp_buf[1], tmp_buf[2], tmp_buf[3],
                tmp_buf[4], tmp_buf[5], tmp_buf[6], tmp_buf[7]);

            item = znx_array_push(res->arr);
            item->type = type;
            item->name = name;
            item->data.data = tmp;
            item->data.len = (size_t)(end - tmp);

            pos = pos + 16;

        } else if (type == ZNX_RESOLVE_CNAME) {
            size_t tmp_pos = pos;
            code = znx_resolver_decode_name(buf, n, &tmp_pos);
    
            if (code != 0) {
                return code;
            }
        
            if (tmp_pos - pos != (size_t)len) {
                return ZNX_RESOLVE_FORMERR;
            }

            pos = pos + (size_t)len;

            str_len = znx_strlen(t_buf);
            u_char  *tmp = znx_thread_malloc(str_len + 1);
            znx_memcpy(tmp, t_buf, str_len);
            tmp[str_len] = '\0';
            znx_cleanup_list_add(&res->cleanup_list, znx_thread_free_handle, tmp);

            item = znx_array_push(res->arr);
            item->type = type;
            item->name = name;
            item->data.data = tmp;
            item->data.len = str_len;

        } else {
            // for unknown types, skip it.
            pos = pos + (size_t)len;
        }
    }

    *posp = pos;
    return 0;
}


// return resolver errno.
// if parse success, we will create znx_resolver_result_t, and return it.
static int
znx_resolver_parse_response(znx_resolver_task_t *task, u_char *buf,
    size_t n, znx_resolver_result_t **result)
{
    if (n < sizeof(znx_resolver_hdr_t)) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: resp truncated",
            &task->query);
        return ZNX_RESOLVE_TRUNC_ERR;
    }

    znx_resolver_task_ctx_t *ctx = task->ctx;
    znx_resolver_hdr_t      *response = (znx_resolver_hdr_t *)buf;

    if (buf[0] != ctx->query[0] ||
        buf[1] != ctx->query[1]) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: id mismatch",
            &task->query);
        return ZNX_RESOLVE_MISMATCH_ERR;
    }

    int flags, nqs, nan, code, qtype, qclass;
    (void)qtype; (void)qclass;

    flags = (response->flags_hi << 8) + response->flags_lo;
    nqs = (response->nqs_hi << 8) + response->nqs_lo;
    nan = (response->nan_hi << 8) + response->nan_lo;

    if ((flags & 0x8000) == 0) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, bad QR flag",
            &task->query);
        return ZNX_RESOLVE_BAD_QR;
    }

    if ((flags & 0x200) != 0) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: resp truncated",
            &task->query);
        return ZNX_RESOLVE_TRUNC_ERR;
    }

    code = flags & 0xf;
    if (code != 0) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: code: %d",
            &task->query, code);
        return code;
    }

    size_t pos = sizeof(znx_resolver_hdr_t);
    code = znx_resolver_decode_name(buf, n, &pos);
    if (code != 0) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: code: %d",
            &task->query, code);
        return code;
    }

    pos += 4;

    // if (pos + (size_t)(nan * 12) > n) {
    //     ZNX_DEF_LOG_ERROR("resolver [%v] failed, err: resp truncated",
    //         &task->query);
    //     return ZNX_RESOLVE_TRUNC_ERR;
    // }

#if 0
    https://github.com/openresty/lua-resty-dns/blob/master/lib/resty/dns/resolver.lua
    authority_section, additional_section
#endif

    znx_resolver_result_t *res = znx_resolver_result_create();
    code = znx_resolver_parse_section(ZNX_RESOLVE_SECTION_AN,
        buf, n, &pos, nan, res, ZNX_FALSE);
    if (code != 0) {
        ZNX_DEF_LOG_ERROR(NULL, "resolver [%v] failed, err: parse_section code: %d",
            &task->query, code);
        znx_resolver_result_free(res);
        return code;
    }

    *result = res;

#if 0
    authority_section, additional_section
#endif

    return 0;
}


static void
znx_resolver_udp_recv(znx_event_t *event)
{
    if (event->timedout) {
        znx_resolver_task_timeout(event);
        return;
    }

    int                     eno;
    znx_result_t            ret;
    znx_resolver_result_t   *result;
    znx_conn_t              *conn = event->data;
    znx_resolver_task_t     *task = conn->data;

    u_char                  buf[ZNX_RESOLVER_UDP_SIZE];

    for ( ;; ) {
        ret = znx_conn_udp_recv(conn, buf, ZNX_RESOLVER_UDP_SIZE);
        if (ret == ZNX_AGAIN) {
            znx_event_loop_enable_read(conn);
            return;
        }

        if (ret < 0 || ret == 0) {
            task->last_eno = ZNX_RESOLVE_SERVFAIL;
            znx_resolver_do_query_done(task, ZNX_RESOLVE_SERVFAIL, NULL);
            return;
        }

        eno = znx_resolver_parse_response(task, buf, (size_t)ret, &result);
        if (eno == 0) {
            znx_resolver_do_query_done(task, 0, result);
            return;
        }

        if (eno == ZNX_RESOLVE_MISMATCH_ERR) {
            continue;
        }

        znx_resolver_do_query_done(task, eno, NULL);
        return;
    }
}


static void
znx_resolver_udp_send(znx_event_t *event)
{
    znx_conn_t                  *conn;
    znx_resolver_task_t         *task;
    znx_resolver_task_ctx_t     *ctx;
    znx_result_t                ret;

    conn = event->data;
    task = conn->data;
    ctx = task->ctx;

    ret = znx_conn_udp_send(conn, ctx->query, ctx->qlen);
    if (ret == (int64_t)ctx->qlen) {
        // enable read.
        znx_event_loop_enable_read(conn);
        return;
    }

    task->last_eno = ZNX_RESOLVE_SERVFAIL;
    znx_resolver_do_query_done(task, ZNX_RESOLVE_SERVFAIL, NULL);
}


static void
znx_resolver_task_connect_done(void *data, znx_conn_t *conn,
    znx_result_t res)
{
    znx_resolver_task_t     *task = data;
    znx_resolver_task_ctx_t *ctx = task->ctx;

    if (res != ZNX_SUCCESS) {
        znx_thread_free(conn);
        task->last_eno = ZNX_RESOLVE_REFUSED;
        znx_resolver_do_task(&task->dispatch_event);
        return;
    }

    ctx->conn = conn;
    conn->data = task;
    conn->read.handler = ctx->tcp_query ?
        znx_resolver_tcp_recv :
        znx_resolver_udp_recv;

    conn->write.handler = ctx->tcp_query ?
        znx_resolver_tcp_send :
        znx_resolver_tcp_recv;

    znx_event_loop_add_timer(&conn->read, task->timeout_ms);

    znx_event_loop_start_io(conn);

    if (task->ctx->tcp_query) {
        znx_resolver_tcp_send(&conn->write);
        return;
    }

    znx_resolver_udp_send(&conn->write);
}


static znx_bool_t
znx_resolver_build_request(znx_resolver_task_t *task)
{
    if (task->qtype == 0) {
        task->qtype = ZNX_RESOLVE_A;
    }

    // reuse query_buf.
    if (task->ctx->query != NULL) {
        return ZNX_TRUE;
    }

    if (task->qtype == ZNX_RESOLVE_A ||
        task->qtype == ZNX_RESOLVE_AAAA) {
        return znx_resolver_build_name_query(task);
    }

    return ZNX_FALSE;
}


static void
znx_resolver_task_connect(znx_resolver_t *resolver,
    znx_resolver_task_t *task, znx_bool_t tcp_query)
{
    znx_resolver_nameserver_t   *nameserver;
    znx_conn_t                  *conn;

    nameserver = resolver->names->elts;
    nameserver = nameserver + (task->step - 1);
    task->ctx->nameserver = nameserver;
    task->ctx->tcp_query = tcp_query;

    if (!znx_resolver_build_request(task)) {
        task->last_eno = ZNX_RESOLVE_INVALID_QUERY;
        znx_resolver_result_t *result =
            znx_resolver_result_create();
        result->eno = ZNX_RESOLVE_INVALID_QUERY;
        znx_single_flight_notify(&resolver->sf,
            &task->dispatch_event, result);
        return;
    }

    size_t tag_len = 0;
    tag_len += 128;
    tag_len += ZNX_SOCKADDR_SIZE;
    tag_len += ZNX_SOCKADDR_SIZE;
    znx_t_buf_grow(tag_len);

    u_char *end = znx_snprintf(t_buf, tag_len,
        "DNS-%d-%s-%s", tcp_query,
        nameserver->address_text,
        nameserver->bind_address_text);

    znx_str_t tag = {
        .data = t_buf,
        .len = (size_t)(end - t_buf)
    };

    conn = znx_t_session_pool_get(&tag, &nameserver->address);
    if (conn != NULL) {
        znx_resolver_task_connect_done(task, conn, ZNX_SUCCESS);
        return;
    }

    znx_address_t *address = &nameserver->address;
    znx_address_t *bind_address = &nameserver->bind_address;

    if (bind_address->sockaddr.sa_family == 0) {
        bind_address = NULL;
    }

    conn = znx_thread_calloc(sizeof(znx_conn_t));
    znx_conn_init(conn, ZNX_CONN_EPOLL_ET);

    if (tcp_query) {
        znx_tcp_connect_setup(conn, address,
            bind_address, NULL, task->timeout_ms,
            znx_resolver_task_connect_done, task);
    } else {
        znx_udp_connect_setup(conn, address, bind_address,
            znx_resolver_task_connect_done, task);
    }
}


static void
znx_resolver_do_task(znx_dispatch_event_t *dispatch_event)
{
    znx_resolver_task_t *task = dispatch_event->data;
    znx_resolver_t *resolver = task->owner_resolver;

    if (task->step == resolver->names->nelts) {
        if (task->last_eno == 0) {
            task->last_eno = ZNX_RESOLVE_UNKNOW_ERR;
        }

        znx_resolver_result_t *result =
            znx_resolver_result_create();
        result->eno = task->last_eno;
        znx_single_flight_notify(&resolver->sf,
            &task->dispatch_event, result);
        return;
    }

    task->step++;

    if (task->ctx == NULL) {
        task->ctx = znx_resolver_task_ctx_create();
    } else {
        znx_resolver_task_ctx_reset(task->ctx);
    }

    znx_resolver_task_connect(resolver, task, task->tcp_query);
}


// if result found in cache, return true.
static znx_bool_t
znx_resolver_query_in_cache(znx_resolver_t *resolver,
    znx_resolver_task_t *task)
{
    znx_bool_t              found;
    znx_resolver_result_t   target;
    znx_resolver_result_t   *result;
    int64_t                 now_ms;

    target.crc = task->crc;
    target.key = task->key;
    
    now_ms = znx_usec_2_msec(get_cache_time());

    int slot = task->crc % ZNX_RESOLVER_CACHE_SLOT;
    znx_spinlock_lock(&resolver->locks[slot]);
    found = znx_slru_get(resolver->slrus[slot], &target, (void **)&result);
    if (found && now_ms > result->expire_time) {
        found = ZNX_FALSE;
        znx_slru_remove(resolver->slrus[slot], &target);
    }
    if (found) {
        znx_resolver_result_hold(result);
    }
    znx_spinlock_unlock(&resolver->locks[slot]);

    if (!found) {
        return ZNX_FALSE;
    }

    task->result = result;

    if (task->ctx != NULL) {
        znx_resolver_task_ctx_destroy(task->ctx);
        task->ctx = NULL;
    }

    task->handler(task);
    return ZNX_TRUE;
}


void
znx_resolver_query(znx_resolver_t *resolver,
    znx_resolver_task_t *task)
{
    task->owner_resolver = resolver;
    if (resolver->names->nelts == 0) {
        znx_resolver_resp_invalid_result(task, ZNX_RESOLVE_NO_NAMES);
        return;
    }

    if (resolver->has_cache && !task->force_update) {
        // found in slru cache.
        if (znx_resolver_query_in_cache(resolver, task)) {
            return;
        }
    }

    // do query
    znx_single_flight_do(&resolver->sf,
        &task->dispatch_event, znx_resolver_do_task);
}


znx_bool_t
znx_testing_resolver_build_name_query(znx_resolver_task_t *task)
{
    return znx_resolver_build_name_query(task);
}

int
znx_testing_resolver_decode_name(u_char *buf, size_t n, size_t *posp)
{
    return znx_resolver_decode_name(buf, n, posp);
}